# Copyright (c) 2008, RPI-ACM, Jesse Kempf <jessekempf@gmail.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# - Redistributions of source code must retain the above copyright notice,
#     this list of conditions and the following disclaimer.
# - Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
# - Neither the names of RPI-ACM nor the names of its contributors
#     may be used to endorse or promote products derived from this software
#     without specific prior written permission.
#
#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
#  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
#  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
#  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
#  OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
#  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
#  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
#  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
#  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
#  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
#  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

"""An interface to read a file in reversed order."""

import os
import os.path

DEBUG = False
PROFILE = False

class ReversedFile(object):
	"""A class suitable for reading data out of a delimited file in reverse
	order.

	The ReversedFile interface is superficially similar to the file interface.
	It provides readline(), but dispenses with many familiar functions,
	including readlines(). Use iterators, etc. etc."""

	def __init__(self, name, delim='\n', binary=False, blocksize=(1024*120)):
		"""Open a file for reverse-order reading.

		The named file will be opened for reading and seek()ed to the
		end."""

		fmode = 'r'
		if binary:
			fmode += 'b'

		self.file = file(name, fmode)
		self.delim = delim
		self.blocksize = blocksize
		self.rawbuf = ""
		self.cookedbuf = []

		# Set our block offset to 0 and position ourselves
		# at the end of the file.
		self.file.seek(0, os.SEEK_END)
		self.initial_offset = self.offset = self.file.tell()

	def __advance_file(self):
		"""Internal function.

		Seek the file to the next block.

		Returns True if there is more data to be read and
		False at EOF."""
		length = self.blocksize

		if self.offset == 0:
			# We have no more data to read.
			return False
		if self.offset < self.blocksize:
			# We're at the final block (beginning) of the file.
			length = self.offset
			self.offset = 0
		else:
			length = self.blocksize
			self.offset -= self.blocksize

		self.file.seek(self.offset, os.SEEK_SET)

		# throw another block onto rawbuf
		self.rawbuf = self.file.read(length) + self.rawbuf

		return True


	def __advance(self):
		"""Internal function.

		Seeks the file to the next block and refreshes the internal
		buffers.
		The buffer rawbuf holds the data as read from the file,
		while cookedbuf holds a list of strings taken from rawbuf
		and chopped up based upon the provided delimiter.

		Returns True if there is more data to be read, and
		False at EOF."""
	
		if self.__advance_file():
			self.cookedbuf = self.rawbuf.split(self.delim)
	
			# Adjust to prevent delimiter proliferation
			# when we add delimiters to all strings.
			if self.rawbuf.endswith(self.delim):
				del self.cookedbuf[-1]
	
			#Replace the delimiter at the end of each line to be returned.
			self.cookedbuf = [x + self.delim for x in self.cookedbuf]

			if self.offset != 0:
				self.rawbuf = self.cookedbuf.pop(0)

			return True
		else:
			return False

	def close(self):
		"""Close the file."""
		self.file.close()

	def fileno(self):
		"""Return the underlying file descriptor."""
		return self.file.fileno()

	def readline(self):
		"""Read the next line from the file, as a string.

		Retains newline. Returns an empty string at EOF."""
		try:
			return self.cookedbuf.pop()
		except IndexError:
			while self.__advance():
				if self.cookedbuf:
					return self.cookedbuf.pop()
			else:
				return ''

	def rewind(self):
		"""Return to the place where the file was first opened."""

		self.file.seek(self.initial_offset, os.SEEK_SET)
		self.offset = self.file.tell()
		self.rawbuf = ""
		self.cookedbuf = []

	def __iter__(self):
		while True:
			line = self.readline()
			if not line:
				break
			yield line
